Conditions | 2 |
Paths | 192 |
Total Lines | 336 |
Lines | 0 |
Ratio | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
1 | /* |
||
55 | $.Autocompleter = function (input, options) { |
||
56 | |||
57 | var KEY = { |
||
58 | UP: 38, |
||
59 | DOWN: 40, |
||
60 | DEL: 46, |
||
61 | TAB: 9, |
||
62 | RETURN: 13, |
||
63 | ESC: 27, |
||
64 | COMMA: 188, |
||
65 | PAGEUP: 33, |
||
66 | PAGEDOWN: 34, |
||
67 | BACKSPACE: 8 |
||
68 | }; |
||
69 | |||
70 | // Create $ object for input element |
||
71 | var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); |
||
72 | |||
73 | var timeout; |
||
74 | var previousValue = ""; |
||
75 | var cache = $.Autocompleter.Cache(options); |
||
76 | var hasFocus = 0; |
||
77 | var lastKeyPressCode; |
||
78 | var config = { |
||
79 | mouseDownOnSelect: false |
||
80 | }; |
||
81 | var select = $.Autocompleter.Select(options, input, selectCurrent, config); |
||
82 | |||
83 | var blockSubmit; |
||
84 | |||
85 | // prevent form submit in opera when selecting with return key |
||
86 | $.browser.opera && $(input.form).bind("submit.autocomplete", function () { |
||
87 | if (blockSubmit) { |
||
88 | blockSubmit = false; |
||
89 | return false; |
||
90 | } |
||
91 | }); |
||
92 | |||
93 | // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all |
||
94 | $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function (event) { |
||
95 | // track last key pressed |
||
96 | lastKeyPressCode = event.keyCode; |
||
97 | switch (event.keyCode) { |
||
98 | |||
99 | case KEY.UP: |
||
100 | event.preventDefault(); |
||
101 | if (select.visible()) { |
||
102 | select.prev(); |
||
103 | } else { |
||
104 | onChange(0, true); |
||
105 | } |
||
106 | break; |
||
107 | |||
108 | case KEY.DOWN: |
||
109 | event.preventDefault(); |
||
110 | if (select.visible()) { |
||
111 | select.next(); |
||
112 | } else { |
||
113 | onChange(0, true); |
||
114 | } |
||
115 | break; |
||
116 | |||
117 | case KEY.PAGEUP: |
||
118 | event.preventDefault(); |
||
119 | if (select.visible()) { |
||
120 | select.pageUp(); |
||
121 | } else { |
||
122 | onChange(0, true); |
||
123 | } |
||
124 | break; |
||
125 | |||
126 | case KEY.PAGEDOWN: |
||
127 | event.preventDefault(); |
||
128 | if (select.visible()) { |
||
129 | select.pageDown(); |
||
130 | } else { |
||
131 | onChange(0, true); |
||
132 | } |
||
133 | break; |
||
134 | |||
135 | // matches also semicolon |
||
136 | case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: |
||
137 | case KEY.TAB: |
||
138 | case KEY.RETURN: |
||
139 | if (selectCurrent()) { |
||
140 | // stop default to prevent a form submit, Opera needs special handling |
||
141 | event.preventDefault(); |
||
142 | blockSubmit = true; |
||
143 | return false; |
||
144 | } |
||
145 | break; |
||
146 | |||
147 | case KEY.ESC: |
||
148 | select.hide(); |
||
149 | break; |
||
150 | |||
151 | default: |
||
152 | clearTimeout(timeout); |
||
153 | timeout = setTimeout(onChange, options.delay); |
||
154 | break; |
||
155 | } |
||
156 | }).focus(function () { |
||
157 | // track whether the field has focus, we shouldn't process any |
||
158 | // results if the field no longer has focus |
||
159 | hasFocus++; |
||
160 | }).blur(function () { |
||
161 | hasFocus = 0; |
||
162 | if (!config.mouseDownOnSelect) { |
||
163 | hideResults(); |
||
164 | } |
||
165 | }).click(function () { |
||
166 | // show select when clicking in a focused field |
||
167 | if (hasFocus++ > 1 && !select.visible()) { |
||
168 | onChange(0, true); |
||
169 | } |
||
170 | }).bind("search", function () { |
||
171 | // TODO why not just specifying both arguments? |
||
172 | var fn = (arguments.length > 1) ? arguments[1] : null; |
||
173 | |||
174 | function findValueCallback(q, data) { |
||
175 | var result; |
||
176 | if (data && data.length) { |
||
177 | for (var i = 0; i < data.length; i++) { |
||
178 | if (data[i].result.toLowerCase() == q.toLowerCase()) { |
||
179 | result = data[i]; |
||
180 | break; |
||
181 | } |
||
182 | } |
||
183 | } |
||
184 | if (typeof fn == "function") fn(result); |
||
185 | else $input.trigger("result", result && [result.data, result.value]); |
||
186 | } |
||
187 | |||
188 | $.each(trimWords($input.val()), function (i, value) { |
||
189 | request(value, findValueCallback, findValueCallback); |
||
190 | }); |
||
191 | }).bind("flushCache", function () { |
||
192 | cache.flush(); |
||
193 | }).bind("setOptions", function () { |
||
194 | $.extend(options, arguments[1]); |
||
195 | // if we've updated the data, repopulate |
||
196 | if ("data" in arguments[1]) |
||
197 | cache.populate(); |
||
198 | }).bind("unautocomplete", function () { |
||
199 | select.unbind(); |
||
200 | $input.unbind(); |
||
201 | $(input.form).unbind(".autocomplete"); |
||
202 | }); |
||
203 | |||
204 | |||
205 | function selectCurrent() { |
||
206 | var selected = select.selected(); |
||
207 | if (!selected) |
||
208 | return false; |
||
209 | |||
210 | var v = selected.result; |
||
211 | previousValue = v; |
||
212 | |||
213 | if (options.multiple) { |
||
214 | var words = trimWords($input.val()); |
||
215 | if (words.length > 1) { |
||
216 | v = words.slice(0, words.length - 1).join(options.multipleSeparator) + options.multipleSeparator + v; |
||
217 | } |
||
218 | v += options.multipleSeparator; |
||
219 | } |
||
220 | |||
221 | $input.val(v); |
||
222 | hideResultsNow(); |
||
223 | $input.trigger("result", [selected.data, selected.value]); |
||
224 | return true; |
||
225 | } |
||
226 | |||
227 | function onChange(crap, skipPrevCheck) { |
||
228 | if (lastKeyPressCode == KEY.DEL) { |
||
229 | select.hide(); |
||
230 | return; |
||
231 | } |
||
232 | |||
233 | var currentValue = $input.val(); |
||
234 | |||
235 | if (!skipPrevCheck && currentValue == previousValue) |
||
236 | return; |
||
237 | |||
238 | previousValue = currentValue; |
||
239 | |||
240 | currentValue = lastWord(currentValue); |
||
241 | if (currentValue.length >= options.minChars) { |
||
242 | $input.addClass(options.loadingClass); |
||
243 | if (!options.matchCase) |
||
244 | currentValue = currentValue.toLowerCase(); |
||
245 | request(currentValue, receiveData, hideResultsNow); |
||
246 | } else { |
||
247 | stopLoading(); |
||
248 | select.hide(); |
||
249 | } |
||
250 | }; |
||
251 | |||
252 | function trimWords(value) { |
||
253 | if (!value) { |
||
254 | return [""]; |
||
255 | } |
||
256 | var words = value.split(options.multipleSeparator); |
||
257 | var result = []; |
||
258 | $.each(words, function (i, value) { |
||
259 | if ($.trim(value)) |
||
260 | result[i] = $.trim(value); |
||
261 | }); |
||
262 | return result; |
||
263 | } |
||
264 | |||
265 | function lastWord(value) { |
||
266 | if (!options.multiple) |
||
267 | return value; |
||
268 | var words = trimWords(value); |
||
269 | return words[words.length - 1]; |
||
270 | } |
||
271 | |||
272 | // fills in the input box w/the first match (assumed to be the best match) |
||
273 | // q: the term entered |
||
274 | // sValue: the first matching result |
||
275 | function autoFill(q, sValue) { |
||
276 | // autofill in the complete box w/the first match as long as the user hasn't entered in more data |
||
277 | // if the last user key pressed was backspace, don't autofill |
||
278 | if (options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE) { |
||
279 | // fill in the value (keep the case the user has typed) |
||
280 | $input.val($input.val() + sValue.substring(lastWord(previousValue).length)); |
||
281 | // select the portion of the value not typed by the user (so the next character will erase) |
||
282 | $.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length); |
||
283 | } |
||
284 | }; |
||
285 | |||
286 | function hideResults() { |
||
287 | clearTimeout(timeout); |
||
288 | timeout = setTimeout(hideResultsNow, 200); |
||
289 | }; |
||
290 | |||
291 | function hideResultsNow() { |
||
292 | var wasVisible = select.visible(); |
||
293 | select.hide(); |
||
294 | clearTimeout(timeout); |
||
295 | stopLoading(); |
||
296 | if (options.mustMatch) { |
||
297 | // call search and run callback |
||
298 | $input.search( |
||
299 | function (result) { |
||
300 | // if no value found, clear the input box |
||
301 | if (!result) { |
||
302 | if (options.multiple) { |
||
303 | var words = trimWords($input.val()).slice(0, -1); |
||
304 | $input.val(words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "")); |
||
305 | } |
||
306 | else |
||
307 | $input.val(""); |
||
308 | } |
||
309 | } |
||
310 | ); |
||
311 | } |
||
312 | if (wasVisible) |
||
313 | // position cursor at end of input field |
||
314 | $.Autocompleter.Selection(input, input.value.length, input.value.length); |
||
315 | }; |
||
316 | |||
317 | function receiveData(q, data) { |
||
318 | if (data && data.length && hasFocus) { |
||
319 | stopLoading(); |
||
320 | select.display(data, q); |
||
321 | autoFill(q, data[0].value); |
||
322 | select.show(); |
||
323 | } else { |
||
324 | hideResultsNow(); |
||
325 | } |
||
326 | }; |
||
327 | |||
328 | function request(term, success, failure) { |
||
329 | if (!options.matchCase) |
||
330 | term = term.toLowerCase(); |
||
331 | var data = cache.load(term); |
||
332 | // recieve the cached data |
||
333 | if (data && data.length) { |
||
334 | success(term, data); |
||
335 | // if an AJAX url has been supplied, try loading the data now |
||
336 | } else if ((typeof options.url == "string") && (options.url.length > 0)) { |
||
337 | |||
338 | var extraParams = { |
||
339 | timestamp: +new Date() |
||
340 | }; |
||
341 | $.each(options.extraParams, function (key, param) { |
||
342 | extraParams[key] = typeof param == "function" ? param() : param; |
||
343 | }); |
||
344 | |||
345 | $.ajax({ |
||
346 | // try to leverage ajaxQueue plugin to abort previous requests |
||
347 | mode: "abort", |
||
348 | // limit abortion to this input |
||
349 | port: "autocomplete" + input.name, |
||
350 | dataType: options.dataType, |
||
351 | url: options.url, |
||
352 | data: $.extend({ |
||
353 | q: lastWord(term), |
||
354 | limit: options.max |
||
355 | }, extraParams), |
||
356 | success: function (data) { |
||
357 | var parsed = options.parse && options.parse(data) || parse(data); |
||
358 | cache.add(term, parsed); |
||
359 | success(term, parsed); |
||
360 | } |
||
361 | }); |
||
362 | } else { |
||
363 | // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match |
||
364 | select.emptyList(); |
||
365 | failure(term); |
||
366 | } |
||
367 | }; |
||
368 | |||
369 | function parse(data) { |
||
370 | var parsed = []; |
||
371 | var rows = data.split("\n"); |
||
372 | for (var i = 0; i < rows.length; i++) { |
||
373 | var row = $.trim(rows[i]); |
||
374 | if (row) { |
||
375 | row = row.split("|"); |
||
376 | parsed[parsed.length] = { |
||
377 | data: row, |
||
378 | value: row[0], |
||
379 | result: options.formatResult && options.formatResult(row, row[0]) || row[0] |
||
380 | }; |
||
381 | } |
||
382 | } |
||
383 | return parsed; |
||
384 | }; |
||
385 | |||
386 | function stopLoading() { |
||
387 | $input.removeClass(options.loadingClass); |
||
388 | }; |
||
389 | |||
390 | }; |
||
391 | |||
766 |